/** @file gui_editbox.cpp
    @brief An editbox widget */

#include "gui/gui_editbox.h"

// Key repetition for editboxes & textboxes
long G3::GUI::DelayBeforeKeyRepetitionKicksIn = 500;
long G3::GUI::KeyRepeatDelay = 100;
G3::TIMER G3::GUI::KeyDelayTimer;

//==============================================================================
// Constructors
//==============================================================================
G3::GUI::EDITBOX::EDITBOX () {
    CaretPos = 0;
    TextWidth = 0;
    CaretWidth = 10;

    LastKey = -1;
    LastModifier = 0;
    RepeatingAKey = false;

    FgVisible = true;
    BgVisible = true;
    CaretVisible = true;

    BgColor [WIDGET::W_UP  ] = COL4F (0.6f, 0.6f, 0.6f, 1.0f);
    FgColor [WIDGET::W_UP  ] = COL4F (0.7f, 0.7f, 0.7f, 1.0f);
    BgColor [WIDGET::W_OVER] = COL4F (0.6f, 0.6f, 0.6f, 1.0f);
    FgColor [WIDGET::W_OVER] = COL4F (1.0f, 1.0f, 1.0f, 1.0f);
    BgColor [WIDGET::W_DOWN] = COL4F (0.7f, 0.7f, 0.7f, 1.0f);
    FgColor [WIDGET::W_DOWN] = COL4F (1.0f, 1.0f, 1.0f, 1.0f);

    CaretColor [WIDGET::W_UP  ] = FgColor [WIDGET::W_OVER];
    CaretColor [WIDGET::W_OVER] = FgColor [WIDGET::W_OVER];
    CaretColor [WIDGET::W_DOWN] = FgColor [WIDGET::W_DOWN];
}

//==============================================================================
// Destructors
//==============================================================================

// Erm, just chill?

//==============================================================================
// = operators
//==============================================================================
G3::GUI::EDITBOX& G3::GUI::EDITBOX::operator= (const EDITBOX &aEditbox) {
    Visible = aEditbox.Visible;
    CaretPos = aEditbox.CaretPos;
    CaretVisible = aEditbox.CaretVisible;
    TextWidth = aEditbox.TextWidth;
    CaretWidth = aEditbox.CaretWidth;
    Rect = aEditbox.Rect;
    Children = aEditbox.Children;

    BgColor [0] = aEditbox.BgColor [0];
    BgColor [1] = aEditbox.BgColor [1];
    BgColor [2] = aEditbox.BgColor [2];
    FgColor [0] = aEditbox.FgColor [0];
    FgColor [1] = aEditbox.FgColor [1];
    FgColor [2] = aEditbox.FgColor [2];
    CaretColor [0] = aEditbox.CaretColor [0];
    CaretColor [1] = aEditbox.CaretColor [1];
    CaretColor [2] = aEditbox.CaretColor [2];
    State = aEditbox.State;
    FgVisible = aEditbox.FgVisible;
    BgVisible = aEditbox.BgVisible;

    // NOTE:: Signals are not copyable

    return *this;
}

//==============================================================================
// Creates the editbox
//==============================================================================
void G3::GUI::EDITBOX::Create (const RECTI &aRect, FONT *aFont) {
    if (Children.empty () || !Children [0]) {
        Children.push_back (new LABEL);

        Assert (Children [0], "EDITBOX::Create: std::vector push_back failed");
    }

    AttachFont (aFont);
    SetValue ("");
    SetRect (aRect);

    if (!Children.empty () && Children [0] != NULL) {
        ((LABEL *) Children [0])->ClippedByRect = true;
    }
}

//==============================================================================
// Sets the rectangle of the editbox
//==============================================================================
void G3::GUI::EDITBOX::SetRect (const RECTI &aRect) {
    Rect.Left = aRect.Left;
    Rect.Top = aRect.Top;
    Rect.Right = aRect.Right;
    Rect.Bottom = aRect.Bottom;

    if (!Children.empty () && Children [0]) {
        Children [0]->SetPosition (VEC2I (Rect.Left + 5, Rect.Top + 5));
        Children [0]->Rect.Right = Rect.Right - 5;

        Rect.Bottom = G3_MAX (aRect.Bottom, Children [0]->Rect.Bottom + 5);
    } else {
        Rect.Right = aRect.Right;
        Rect.Bottom = aRect.Bottom;
    }
}

//==============================================================================
// Shrinks the editbox to its children
//==============================================================================
void G3::GUI::EDITBOX::Shrink () {
    Assert (Children [0], "EDITBOX::Shrink: You should call Create on the editbox first..");

    ((LABEL *) Children [0])->Shrink ();

    Rect.Right = G3_MAX (Rect.Right, Children [0]->Rect.Right + 5);
    Rect.Bottom = G3_MAX (Rect.Bottom, Children [0]->Rect.Bottom + 5);
}

//==============================================================================
// Attaches the editbox a font
//==============================================================================
void G3::GUI::EDITBOX::AttachFont (G3::FONT *aFont) {
    Assert (Children [0], "EDITBOX::AttachFont: You should call Create on the editbox first..");

    ((LABEL *) Children [0])->AttachFont (aFont);
    TextWidth = aFont->CalcTextWidth (GetValue ());
}

//==============================================================================
// Sets the editbox value
//==============================================================================
void G3::GUI::EDITBOX::SetValue (const string &aValue) {
    Assert (Children [0], "EDITBOX::SetValue: You should call Create on the editbox first..");
    Assert (((LABEL *) Children [0])->Font, "EDITBOX::Draw: Child's Font member is a NULL pointer");

    FONT *font = ((LABEL *) Children [0])->Font;

    ((LABEL *) Children [0])->SetValue (aValue);
    TextWidth = font->CalcTextWidth (GetValue ());
    CaretPos = aValue.size ();

    SigValueChanged (this);
}

//==============================================================================
// Processes character input.
//
//      c               Input character
//
//      m               Modifier flags
//==============================================================================
void G3::GUI::EDITBOX::ProcCharInput (int c, short m) {
    Assert (Children [0], "EDITBOX::AppendValue: You should call Create on the editbox first..");
    Assert (((LABEL *) Children [0])->Font, "EDITBOX::Draw: Child's Font member is a NULL pointer");

    // Restart the timer
    KeyDelayTimer.Start ();

    std::string new_char = "", old_str = "", old_str1 = "", old_str2 = "";
    int OldCaretPos = 0;

    // Get the font
    FONT *font = ((LABEL *) Children [0])->Font;

    // Store the character in an std::string
    new_char += (char) c;

    // Get the old string
    old_str = GetValue ();
    if (old_str.size () > 0) {
        old_str1 = old_str.substr (0, CaretPos);

        if (old_str.size () > 1 && CaretPos < (int) old_str.size ())
            old_str2 = old_str.substr (CaretPos, std::string::npos);
    }

    // Backspace was pressed?
    if (new_char == "\b") {
        // Remove the last character from the old string
        if (old_str1.size () > 1)
            old_str1 = old_str1.substr (0, old_str1.size () - 1);
        else
            old_str1 = "";

        OldCaretPos = CaretPos;
        SetValue (old_str1 + old_str2);
        CaretPos = OldCaretPos - 1;

        if (CaretPos < 0)
            CaretPos = 0;
    // Delete was pressed?
    } else if (c == SDLK_DELETE) {
        // Remove the last character from the old string
        if (old_str2.size () > 1)
            old_str2 = old_str2.substr (1, old_str2.size () - 1);
        else
            old_str2 = "";

        OldCaretPos = CaretPos;
        SetValue (old_str1 + old_str2);
        CaretPos = OldCaretPos;
    // Enter pressed?
    } else if (new_char == "\r") {
        // Send a signal
        SigEnterPressed (this);
    // A text character was pressed?
    } else if (new_char >= " " && new_char <= "z") {
        // Change case
        if (m & KMOD_LSHIFT || m & KMOD_CAPS)
            std::transform (new_char.begin (), new_char.end (), new_char.begin (), toupper);
        // Add it
        SetValue (old_str1 + new_char + old_str2);

        CaretPos++;

        if (CaretPos > (int) old_str.size () + 1)
            CaretPos = old_str.size () + 1;
    // Left arrwo was pressed?
    } else if (c == SDLK_LEFT) {
        CaretPos--;

        if (CaretPos < 0)
            CaretPos = 0;

        return;
    // Right arrow was pressed?
    } else if (c == SDLK_RIGHT) {
        CaretPos++;

        if (CaretPos > (int) old_str.size ())
            CaretPos = old_str.size ();

        return;
    }

    // Recalculate text width, caret position
    TextWidth = font->CalcTextWidth (GetValue ());

    // And send a 'change' signal
    SigValueChanged (this);
}

//==============================================================================
// Gets the textbox value
//==============================================================================
string G3::GUI::EDITBOX::GetValue () {
    Assert (Children [0], "EDITBOX::GetValue: You should call Create on the editbox first..");

    return ((LABEL *) Children [0])->Value;
}

//==============================================================================
// Draws the editbox
//==============================================================================
void G3::GUI::EDITBOX::Draw (float aElapsedTime) {
    Assert (Children [0], "EDITBOX::GetValue: You should call Create on the editbox first..");

    if (!Visible)
        return;

    Assert (State <= 2, "EDITBOX::Draw: State member is invalid (< 0 || > 2)");

    // Key repetition
    if (LastKey >= 0) {
        if (KeyDelayTimer.GetElapsedTime () >= DelayBeforeKeyRepetitionKicksIn ||
            (RepeatingAKey && KeyDelayTimer.GetElapsedTime () >= KeyRepeatDelay)) {
            RepeatingAKey = true;

            ProcCharInput (LastKey, LastModifier);
        }
    }

    int CharWidth = CaretWidth;

    if (GetValue ().size () > 0)
        CharWidth = TextWidth / GetValue ().size ();

    glPushMatrix ();
    glDisable (GL_TEXTURE_2D);

    // Editbox frame
    if (FgVisible) {
        // Editbox Foreground color
        glColor4f (FgColor [State].r, FgColor [State].g, FgColor [State].b, FgColor [State].a);

        glBegin (GL_LINE_STRIP);
        glVertex2i (Rect.Left, -Rect.Top);
        glVertex2i (Rect.Left, -Rect.Bottom);
        glVertex2i (Rect.Right, -Rect.Bottom);
        glVertex2i (Rect.Right, -Rect.Top);
        glVertex2i (Rect.Left, -Rect.Top);
        glEnd ();
    }

    // Editbox background
    if (BgVisible) {
        // Editbox Background color
        glColor4f (BgColor [State].r, BgColor [State].g, BgColor [State].b, BgColor [State].a);

        glBegin (GL_TRIANGLE_STRIP);
        glVertex2i (Rect.Left + 2, -Rect.Top - 2);
        glVertex2i (Rect.Left + 2, -Rect.Bottom + 2);
        glVertex2i (Rect.Right - 2, -Rect.Top - 2);
        glVertex2i (Rect.Right - 2, -Rect.Bottom + 2);
        glEnd ();
    }

    // Draw caret
    if (CaretVisible) {
        // Editbox Foreground color
        glColor4f (CaretColor [State].r, CaretColor [State].g, CaretColor [State].b, CaretColor [State].a);

        glBegin (GL_LINES);
        glVertex2i (Rect.Left + 5 + CaretPos * CharWidth + CaretWidth / 2, -Rect.Bottom + 2);
        glVertex2i (Rect.Left + 5 + CaretPos * CharWidth + CaretWidth + CaretWidth / 2, -Rect.Bottom + 2);
        glEnd ();
    }

    glEnable (GL_TEXTURE_2D);

    // Draw text on it
    if (!Children.empty () && Children [0]) {
        Assert (((LABEL *) Children [0])->Font, "EDITBOX::Draw: Child's Font member is a NULL pointer");

        Children [0]->Draw (aElapsedTime);
    }

    glPopMatrix ();
}

//==============================================================================
// Sets a callback for an editbox contents change event
//==============================================================================
G3::GUI::EDITBOX::SIG_CONNECTION G3::GUI::EDITBOX::doOnValueChange (CHANGE_HANDLER aChangeHandler) {
    Assert (aChangeHandler, "EDITBOX::doOnValueChange: ChangeHandler argument is a NULL pointer");

    SIG_CONNECTION c = SigValueChanged.connect (aChangeHandler);
    ConnValueChanged.push_back (c);

    return c;
}

//==============================================================================
// Sets a callback for an Enter pressed event in the editbox
//==============================================================================
G3::GUI::EDITBOX::SIG_CONNECTION G3::GUI::EDITBOX::doOnEnterPressed (CHANGE_HANDLER aChangeHandler) {
    Assert (aChangeHandler, "EDITBOX::doOnEnterPressed: ChangeHandler argument is a NULL pointer");

    SIG_CONNECTION c = SigEnterPressed.connect (aChangeHandler);
    ConnEnterPressed.push_back (c);

    return c;
}
